# Copyright (C) 2016 D Levin (http://www.kfrlib.com)
# This file is part of KFR
#
# KFR is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# KFR is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with KFR.

cmake_minimum_required(VERSION 3.1)

set(CMAKE_CXX_FLAGS
    " ${CMAKE_CXX_FLAGS}"
    CACHE STRING "compile flags" FORCE)

project(kfr CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS ON)

if (WIN32 AND CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
    set(CMAKE_INSTALL_PREFIX
        ""
        CACHE STRING "Reset install prefix on Win32" FORCE)
endif ()

if (WIN32)
    set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
endif ()

set(CMAKE_CXX_VISIBILITY_PRESET "default")
set(CMAKE_C_VISIBILITY_PRESET "default")

message(STATUS "Install prefix = ${CMAKE_INSTALL_PREFIX}")

message(
    STATUS
        "C++ compiler: ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION} ${CMAKE_CXX_COMPILER} "
)
message(STATUS CMAKE_SYSTEM_PROCESSOR = ${CMAKE_SYSTEM_PROCESSOR})

if (CMAKE_SYSTEM_PROCESSOR MATCHES "(x86)|(X86)|(amd64)|(AMD64)")
    set(X86 TRUE)
else ()
    set(X86 FALSE)
endif ()

if (X86)
    message(STATUS X86)
endif ()

if (MSVC)
    message(STATUS MSVC)
endif ()

if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL
                                              "AppleClang")
    set(CLANG 1)
else ()
    set(CLANG 0)
endif ()

# Include autogenerated list of source files
include(sources.cmake)

option(ENABLE_TESTS "Enable tests and examples" OFF)
if (CLANG)
    option(ENABLE_DFT "Enable DFT and related algorithms." ON)
    option(ENABLE_DFT_NP "Enable Non-power of 2 DFT" ON)
    if (X86)
        option(
            ENABLE_DFT_MULTIARCH
            "Build DFT static libraries for various architectures. Requires Clang"
            OFF)
    endif ()
else ()
    option(ENABLE_DFT "Enable DFT and related algorithms." OFF)
    option(ENABLE_DFT_NP "Enable Non-power of 2 DFT" OFF)
endif ()
option(ENABLE_ASMTEST "Enable writing disassembly" OFF)
option(REGENERATE_TESTS "Regenerate auto tests" OFF)
option(DISABLE_CLANG_EXTENSIONS "Disable Clang vector extensions" OFF)
option(KFR_EXTENDED_TESTS "Extended tests (up to hour)" OFF)
option(SKIP_TESTS "Skip tests (only build)" OFF)
mark_as_advanced(ENABLE_ASMTEST)
mark_as_advanced(REGENERATE_TESTS)
mark_as_advanced(DISABLE_CLANG_EXTENSIONS)

if (NOT CPU_ARCH)
    set(CPU_ARCH detect)
endif ()

if (CPU_ARCH STREQUAL "detect" AND X86)
    message(STATUS "Detecting native cpu...")
    try_run(
        RUN_RESULT COMPILE_RESULT "${CMAKE_CURRENT_BINARY_DIR}/tmpdir"
        ${CMAKE_CURRENT_SOURCE_DIR}/cmake/detect_cpu.cpp
        CMAKE_FLAGS
            "-DINCLUDE_DIRECTORIES=${CMAKE_CURRENT_SOURCE_DIR}/include"
            -DCMAKE_CXX_STANDARD=17 -DCMAKE_CXX_STANDARD_REQUIRED=ON
            -DCMAKE_CXX_EXTENSIONS=ON
        COMPILE_OUTPUT_VARIABLE COMPILE_OUT
        RUN_OUTPUT_VARIABLE RUN_OUT)
    if (COMPILE_RESULT AND RUN_RESULT EQUAL 0)
        message(STATUS DETECTED_CPU = ${RUN_OUT})
        set(CPU_ARCH
            ${RUN_OUT}
            CACHE STRING "Detected CPU" FORCE)
    else ()
        message(STATUS COMPILE_RESULT = ${COMPILE_RESULT})
        message(STATUS RUN_RESULT = ${RUN_RESULT})
        message(STATUS COMPILE_OUT = ${COMPILE_OUT})
        message(STATUS RUN_OUT = ${RUN_OUT})
    endif ()
endif ()

include(cmake/target_set_arch.cmake)

add_library(use_arch INTERFACE)
target_set_arch(use_arch INTERFACE ${CPU_ARCH})

if (WIN32)
    add_definitions(-D_CRT_SECURE_NO_WARNINGS)
    add_definitions(-D_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS)
    add_definitions(-D_ENABLE_EXTENDED_ALIGNED_STORAGE)
endif ()

if (IOS)
    set(STD_LIB)
else ()
    set(STD_LIB stdc++)
endif ()

if (ANDROID)
    set(PTHREAD_LIB)
else ()
    set(PTHREAD_LIB pthread)
endif ()

# KFR library
add_library(kfr INTERFACE)
target_sources(kfr INTERFACE ${KFR_SRC})
target_include_directories(kfr INTERFACE include)
target_compile_options(kfr INTERFACE "$<$<CONFIG:DEBUG>:-DKFR_DEBUG>")
if (APPLE)
    target_compile_options(kfr INTERFACE -faligned-allocation)
endif ()
if (NOT IOS)
    if (CLANG)
        target_compile_options(kfr INTERFACE -Xclang -mstackrealign)
    elseif (NOT MSVC)
        target_compile_options(kfr INTERFACE -mstackrealign)
    endif ()
endif ()
if (MSVC)
    target_compile_options(kfr INTERFACE -bigobj -EHsc)
else ()
    target_link_libraries(kfr INTERFACE ${STD_LIB} ${PTHREAD_LIB} m)
endif ()
if (DISABLE_CLANG_EXTENSIONS)
    target_compile_definitions(kfr INTERFACE -DCMT_DISABLE_CLANG_EXT)
endif ()
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
    target_compile_options(kfr INTERFACE -Wno-ignored-qualifiers)
endif ()
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
    target_compile_options(kfr INTERFACE -Wno-c++1z-extensions)
endif ()

if (NOT ENABLE_DFT)
    target_compile_definitions(kfr INTERFACE -DKFR_NO_DFT)
endif ()
if (KFR_EXTENDED_TESTS)
    target_compile_definitions(kfr INTERFACE -DKFR_EXTENDED_TESTS)
endif ()

message(STATUS CPU_ARCH=${CPU_ARCH})

if (X86)
    add_executable(detect_cpu ${CMAKE_CURRENT_SOURCE_DIR}/cmake/detect_cpu.cpp)
    target_link_libraries(detect_cpu PRIVATE kfr)
    target_set_arch(detect_cpu PRIVATE generic)
endif ()

function (add_arch_library NAME ARCH SRCS DEFS)
    add_library(${NAME}_${ARCH} ${SRCS})
    target_link_libraries(${NAME}_${ARCH} kfr)
    target_set_arch(${NAME}_${ARCH} PRIVATE ${ARCH})
    target_compile_options(${NAME}_${ARCH} PRIVATE ${DEFS})
    target_link_libraries(${NAME}_all INTERFACE ${NAME}_${ARCH})
    target_compile_options(${NAME}_${ARCH} PRIVATE -flto)
endfunction ()

if (ENABLE_DFT)

    if (MSVC)
        set(KFR_DFT_DEFS -fp:fast)
    else ()
        set(KFR_DFT_DEFS -ffast-math)
    endif ()

    if (ENABLE_DFT_MULTIARCH)
        add_library(kfr_dft INTERFACE)
        add_library(kfr_dft_all INTERFACE)
        target_link_libraries(kfr_dft INTERFACE kfr kfr_dft_all)
        target_compile_definitions(
            kfr_dft
            INTERFACE -DKFR_DFT_MULTI=1
                      -DCMT_MULTI=1
                      -DCMT_MULTI_ENABLED_SSE2=1
                      -DCMT_MULTI_ENABLED_SSE41=1
                      -DCMT_MULTI_ENABLED_AVX=1
                      -DCMT_MULTI_ENABLED_AVX2=1
                      -DCMT_MULTI_ENABLED_AVX512=1)

        add_arch_library(kfr_dft sse2 "${KFR_DFT_SRC}" "${KFR_DFT_DEFS}")
        add_arch_library(kfr_dft sse41 "${KFR_DFT_SRC}" "${KFR_DFT_DEFS}")
        add_arch_library(kfr_dft avx "${KFR_DFT_SRC}" "${KFR_DFT_DEFS}")
        add_arch_library(kfr_dft avx2 "${KFR_DFT_SRC}" "${KFR_DFT_DEFS}")
        add_arch_library(kfr_dft avx512 "${KFR_DFT_SRC}" "${KFR_DFT_DEFS}")

    else ()
        add_library(kfr_dft ${KFR_DFT_SRC})
        target_link_libraries(kfr_dft kfr use_arch)
        target_compile_options(kfr_dft PRIVATE "${KFR_DFT_DEFS}")
        if (ENABLE_DFT_NP)
            target_compile_definitions(kfr_dft PUBLIC -DKFR_DFT_NPo2)
        else ()
            target_compile_definitions(kfr_dft PUBLIC -DKFR_DFT_NO_NPo2)
        endif ()

    endif ()

    if (ENABLE_CAPI_BUILD)
        add_subdirectory(capi)
    endif ()
endif ()

if (ENABLE_TESTS)
    add_subdirectory(examples)
    add_subdirectory(tests)
    add_subdirectory(tools)
endif ()

add_library(kfr_io ${KFR_IO_SRC})
target_link_libraries(kfr_io kfr)
target_link_libraries(kfr_io use_arch)

install(
    TARGETS kfr kfr_io
    ARCHIVE DESTINATION lib
    LIBRARY DESTINATION lib
    RUNTIME DESTINATION bin)

if (ENABLE_CAPI_BUILD)
    install(
        TARGETS kfr_capi
        ARCHIVE DESTINATION lib
        LIBRARY DESTINATION lib
        RUNTIME DESTINATION bin)
endif ()

set(kfr_defines)

function(append_defines_from target)
    get_target_property(compile_defs ${target} INTERFACE_COMPILE_DEFINITIONS)
    if (compile_defs)
        list(APPEND kfr_defines "${compile_defs}")
    endif ()
    set(kfr_defines ${kfr_defines} PARENT_SCOPE)
endfunction()

append_defines_from(kfr)
if (ENABLE_DFT)
    append_defines_from(kfr_dft)
endif ()
append_defines_from(kfr_io)

message(STATUS kfr_defines = "${kfr_defines}")
string(REPLACE "=" " " kfr_defines "${kfr_defines}")
string(REPLACE ";" "\n#define " kfr_defines "${kfr_defines}")
set(kfr_defines "#define ${kfr_defines}\n")

file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/kfr_config.h "${kfr_defines}")

if (ENABLE_DFT)
    if (ENABLE_DFT_MULTIARCH)
        install(
            TARGETS kfr_dft_sse2 kfr_dft_sse41 kfr_dft_avx kfr_dft_avx2
                    kfr_dft_avx512
            ARCHIVE DESTINATION lib
            LIBRARY DESTINATION lib
            RUNTIME DESTINATION bin)
    else ()
        install(
            TARGETS kfr_dft
            ARCHIVE DESTINATION lib
            LIBRARY DESTINATION lib
            RUNTIME DESTINATION bin)
    endif ()
endif ()

install(DIRECTORY include/kfr DESTINATION include)

install(
    FILES ${CMAKE_CURRENT_BINARY_DIR}/kfr_config.h
    DESTINATION include/kfr
    RENAME config.h
    )

# uninstall target
if (NOT TARGET uninstall)
    configure_file(
        "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in"
        "${CMAKE_CURRENT_BINARY_DIR}/cmake/cmake_uninstall.cmake" IMMEDIATE
        @ONLY)

    add_custom_target(
        uninstall
        COMMAND ${CMAKE_COMMAND} -P
                ${CMAKE_CURRENT_BINARY_DIR}/cmake/cmake_uninstall.cmake)
endif ()

if (DEBUG_CMAKE)
    get_cmake_property(_variableNames VARIABLES)
    list(SORT _variableNames)
    foreach (_variableName ${_variableNames})
        message(STATUS "${_variableName}=${${_variableName}}")
    endforeach ()
endif ()